package top.v5it.japi.plus.common.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 对象转换工具类
 *
 * @author zhanpu
 * @date 2021/4/21
 */
public class OrikaUtil {

    private static final MapperFactory MAPPER_FACTORY;

    private OrikaUtil() {
        throw new UnsupportedOperationException();
    }

    static {
        MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
    }

    /**
     * 注册转换器，转换器需实现{@link Converter}接口
     *
     * @param converterId 转换器id
     * @param converter   转换器{@link Converter}
     * @param <S>         原始数据类型
     * @param <D>         目标数据类型
     */
    public static <S, D> void registerConverter(String converterId, Converter<S, D> converter) {
        if (!MAPPER_FACTORY.getConverterFactory().hasConverter(converterId)) {
            MAPPER_FACTORY.getConverterFactory().registerConverter(converterId, converter);
        }
    }

    /**
     * 将对象a转换成对象b，要求两对象字段一样
     *
     * @param source 原数据
     * @param dest   目标数据类型
     * @param <A>    A类型
     * @param <B>    B类型
     * @return B类型对象
     */
    public static <A, B> B converter(A source, Class<B> dest) {
        try {
            return MAPPER_FACTORY.getMapperFacade().map(source, dest);
        } finally {
            // 重新设置mapperFacade，保证转换器正常注册
            MAPPER_FACTORY.getConverterFactory().setMapperFacade(null);
        }
    }

    /**
     * 将对象a集合转换成对象b集合，要求两对象字段一样
     *
     * @param source 原数据
     * @param dest   目标数据类型
     * @param <A>    A类型
     * @param <B>    B类型
     * @return B类型对象集合
     */
    public static <A, B> List<B> converter(List<A> source, Class<B> dest) {
        try {
            return MAPPER_FACTORY.getMapperFacade().mapAsList(source, dest);
        } finally {
            // 重新设置mapperFacade，保证转换器正常注册
            MAPPER_FACTORY.getConverterFactory().setMapperFacade(null);
        }
    }

    /**
     * 将对象中带“_”字段转换成驼峰式
     *
     * @param source            原数据
     * @param dest              目标数据类型
     * @param converterFieldMap 转换字段，一般应用于转换对象中两边字段数据类型不一致情况，key为字段名，value为转换器id
     * @param excludes          排除字段
     * @param <A>               A类型
     * @param <B>               B类型
     * @return B类型对象
     */
    @SuppressWarnings("unchecked")
    public static <A, B> B underlineCase2CamelCaseConverter(A source, Class<B> dest, Map<String, String> converterFieldMap, String... excludes) {
        Class<A> clazz = (Class<A>) source.getClass();
        Map<String, String> fieldMap = toCamelCase(clazz, excludes);
        register(clazz, dest, fieldMap, converterFieldMap);

        return converter(source, dest);
    }

    /**
     * 将对象中带“_”字段转换成驼峰式
     *
     * @param source            原数据
     * @param dest              目标数据类型
     * @param converterFieldMap 转换字段，一般应用于转换对象中两边字段数据类型不一致情况，key为字段名，value为转换器id
     * @param excludes          排除字段
     * @param <A>               A类型
     * @param <B>               B类型
     * @return B类型对象集合
     */
    @SuppressWarnings("unchecked")
    public static <A, B> List<B> underlineCase2CamelCaseConverter(List<A> source, Class<B> dest, Map<String, String> converterFieldMap, String... excludes) {
        Class<A> clazz = (Class<A>) source.get(0).getClass();
        Map<String, String> fieldMap = toCamelCase(clazz, excludes);
        register(clazz, dest, fieldMap, converterFieldMap);

        return converter(source, dest);
    }

    /**
     * 将对象中驼峰式转换成带“_”字段
     *
     * @param source            原数据
     * @param dest              目标数据类型
     * @param converterFieldMap 转换字段，一般应用于转换对象中两边字段数据类型不一致情况，key为字段名，value为转换器id
     * @param excludes          排除字段
     * @param <A>               A类型
     * @param <B>               B类型
     * @return B类型对象
     */
    @SuppressWarnings("unchecked")
    public static <A, B> B camelCase2underlineCaseConverter(A source, Class<B> dest, Map<String, String> converterFieldMap, String... excludes) {
        Class<A> clazz = (Class<A>) source.getClass();
        Map<String, String> fieldMap = toUnderlineCase(clazz, excludes);
        register(clazz, dest, fieldMap, converterFieldMap);

        return converter(source, dest);
    }

    /**
     * 将对象中驼峰式转换成带“_”字段
     *
     * @param source            原数据
     * @param dest              目标数据类型
     * @param converterFieldMap 转换字段，一般应用于转换对象中两边字段数据类型不一致情况，key为字段名，value为转换器id
     * @param excludes          排除字段
     * @param <A>               A类型
     * @param <B>               B类型
     * @return B类型对象集合
     */
    @SuppressWarnings("unchecked")
    public static <A, B> List<B> camelCase2underlineCaseConverter(List<A> source, Class<B> dest, Map<String, String> converterFieldMap, String... excludes) {
        Class<A> clazz = (Class<A>) source.get(0).getClass();
        Map<String, String> fieldMap = toUnderlineCase(clazz, excludes);
        register(clazz, dest, fieldMap, converterFieldMap);

        return converter(source, dest);
    }

    /**
     * A转换B
     *
     * @param source   原数据
     * @param dest     目标数据类型
     * @param fieldMap 转换字段，key为A类中字段名，value为B类中字段名
     * @param <A>      A类型
     * @param <B>      B类型
     * @return B类型对象
     */
    @SuppressWarnings("unchecked")
    public static <A, B> B converter(A source, Class<B> dest, Map<String, String> fieldMap) {
        Class<A> clazz = (Class<A>) source.getClass();
        register(clazz, dest, fieldMap);

        return converter(source, dest);
    }

    /**
     * A转换B
     *
     * @param source   原数据
     * @param dest     目标数据类型
     * @param fieldMap 转换字段，key为A类中字段名，value为B类中字段名
     * @param <A>      A类型
     * @param <B>      B类型
     * @return B类型对象集合
     */
    @SuppressWarnings("unchecked")
    public static <A, B> List<B> converter(List<A> source, Class<B> dest, Map<String, String> fieldMap) {
        Class<A> clazz = (Class<A>) source.get(0).getClass();
        register(clazz, dest, fieldMap);

        return converter(source, dest);
    }

    /**
     * 注册转换字段
     *
     * @param source   原数据类型
     * @param dest     目标数据类型
     * @param fieldMap 转换字段，key为A类中字段名，value为B类中字段名
     * @param <A>      A类型
     * @param <B>      B类型
     */
    private static <A, B> void register(Class<A> source, Class<B> dest, Map<String, String> fieldMap) {
        register(() -> MAPPER_FACTORY.classMap(source, dest), fieldMap);
    }

    /**
     * 注册转换字段
     *
     * @param source            原数据类型
     * @param dest              目标数据类型
     * @param fieldMap          转换字段，key为A类中字段名，value为B类中字段名
     * @param converterFieldMap 转换字段，一般应用于转换对象中两边字段数据类型不一致情况，key为字段名，value为转换器id
     * @param <A>               A类型
     * @param <B>               B类型
     */
    private static <A, B> void register(Class<A> source, Class<B> dest, Map<String, String> fieldMap, Map<String, String> converterFieldMap) {
        register(() -> MAPPER_FACTORY.classMap(source, dest), fieldMap, converterFieldMap);
    }

    /**
     * 注册转换字段
     *
     * @param supplier 函数
     * @param fieldMap 转换字段，key为A类中字段名，value为B类中字段名
     * @param <A>      A类型
     * @param <B>      B类型
     */
    private static <A, B> void register(Supplier<ClassMapBuilder<A, B>> supplier, Map<String, String> fieldMap) {
        ClassMapBuilder<A, B> classMapBuilder = supplier.get();
        register(v -> classMapBuilder.field(v.getKey(), v.getValue()), fieldMap);
        classMapBuilder.byDefault().register();
    }

    /**
     * 注册转换字段
     *
     * @param supplier          函数
     * @param fieldMap          转换字段，key为A类中字段名，value为B类中字段名
     * @param converterFieldMap 转换字段，一般应用于转换对象中两边字段数据类型不一致情况，key为字段名，value为转换器id
     * @param <A>               A类型
     * @param <B>               B类型
     */
    private static <A, B> void register(Supplier<ClassMapBuilder<A, B>> supplier, Map<String, String> fieldMap, Map<String, String> converterFieldMap) {
        ClassMapBuilder<A, B> classMapBuilder = supplier.get();
        register(v -> {
            if (!converterFieldMap.containsKey(v.getKey())) {
                classMapBuilder.field(v.getKey(), v.getValue());
            }
        }, fieldMap);

        register(v -> classMapBuilder.fieldMap(v.getKey(), fieldMap.get(v.getKey())).converter(v.getValue()).add(), converterFieldMap);
        classMapBuilder.byDefault().register();
    }

    /**
     * 注册转换字段
     *
     * @param consumer 函数
     * @param fieldMap 转换字段，key为A类中字段名，value为B类中字段名
     */
    private static void register(Consumer<Map.Entry<String, String>> consumer, Map<String, String> fieldMap) {
        fieldMap.entrySet().iterator().forEachRemaining(consumer);
    }

    /**
     * 将对象中带“_”字段转换成驼峰式
     *
     * @param source   原数据
     * @param dest     目标数据类型
     * @param excludes 排除字段
     * @param <A>      A类型
     * @param <B>      B类型
     * @return B类型对象
     */
    @SuppressWarnings("unchecked")
    public static <A, B> B underlineCase2CamelCaseConverter(A source, Class<B> dest, String... excludes) {
        Class<A> clazz = (Class<A>) source.getClass();
        return converter(source, dest, toCamelCase(clazz, excludes));
    }

    /**
     * 将对象中带“_”字段转换成驼峰式
     *
     * @param source   原数据
     * @param dest     目标数据类型
     * @param excludes 排除字段
     * @param <A>      A类型
     * @param <B>      B类型
     * @return B类型对象集合
     */
    @SuppressWarnings("unchecked")
    public static <A, B> List<B> underlineCase2CamelCaseConverter(List<A> source, Class<B> dest, String... excludes) {
        Class<A> clazz = (Class<A>) source.get(0).getClass();
        return converter(source, dest, toCamelCase(clazz, excludes));
    }

    /**
     * 将对象中驼峰式转换成带“_”字段
     *
     * @param source   原数据
     * @param dest     目标数据类型
     * @param excludes 排除字段
     * @param <A>      A类型
     * @param <B>      B类型
     * @return B类型对象
     */
    @SuppressWarnings("unchecked")
    public static <A, B> B camelCase2underlineCaseConverter(A source, Class<B> dest, String... excludes) {
        Class<A> clazz = (Class<A>) source.getClass();
        return converter(source, dest, toUnderlineCase(clazz, excludes));
    }

    /**
     * 将对象中驼峰式转换成带“_”字段
     *
     * @param source   原数据
     * @param dest     目标数据类型
     * @param excludes 排除字段
     * @param <A>      A类型
     * @param <B>      B类型
     * @return B类型对象集合
     */
    @SuppressWarnings("unchecked")
    public static <A, B> List<B> camelCase2underlineCaseConverter(List<A> source, Class<B> dest, String... excludes) {
        Class<A> clazz = (Class<A>) source.get(0).getClass();
        return converter(source, dest, toUnderlineCase(clazz, excludes));
    }


    /*********************************************************************************************************************************************************************/


    /**
     * default exclude serialVersionUID
     */
    private static final String SERIAL_VERSION_UID_EXCLUDE = "serialVersionUID";

    /**
     * 将下划线方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空，则返回空字符串
     *
     * @param clazz    类型
     * @param excludes 排除字段
     * @return {@link Map<String, String>} key为类中原字段名，value为转换后字段名
     */
    public static Map<String, String> toCamelCase(Class<?> clazz, String... excludes) {
        return toMap(clazz, StrUtil::toCamelCase, excludes);
    }

    /**
     * 将驼峰式命名的字符串转换为下划线方式。如果转换前的驼峰式命名的字符串为空，则返回空字符串
     *
     * @param clazz    类型
     * @param excludes 排除字段
     * @return {@link Map<String, String>} key为类中原字段名，value为转换后字段名
     */
    public static Map<String, String> toUnderlineCase(Class<?> clazz, String... excludes) {
        return toMap(clazz, StrUtil::toUnderlineCase, excludes);
    }

    /**
     * 通过反射获取类中的字段，并转换
     *
     * @param clazz    类型
     * @param func     转换函数
     * @param excludes 排除字段
     * @return {@link Map<String, String>} key为类中原字段名，value为转换后字段名
     */
    public static Map<String, String> toMap(Class<?> clazz, Function<String, String> func, String... excludes) {
        Set<String> excludesSet = getExcludes(excludes);

        Field[] fields = ReflectUtil.getFields(clazz);
        Map<String, String> fieldMap = new HashMap<>(fields.length - excludes.length);

        for (Field field : fields) {
            String name = field.getName();

            if (!excludesSet.add(name)) {
                continue;
            }

            String camelCase = func.apply(name);
            fieldMap.put(name, camelCase);
        }

        return fieldMap;
    }

    /**
     * 将排除字段转换成{@link Set<String>}
     *
     * @param excludes 排除字段
     * @return {@link Set<String>}
     */
    private static Set<String> getExcludes(String... excludes) {
        return Stream.of(excludes).allMatch(StrUtil::isBlank) ? CollectionUtil.newHashSet(SERIAL_VERSION_UID_EXCLUDE) : Stream.of(excludes).collect(Collectors.toSet());
    }
}
